﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Diagnostics;
using System.Threading;

namespace Bohdan.Arch.Messaging
{
    public class MessagingBus : IDisposable
    {
        protected Dictionary<Guid, GeneralSubscriber> _Subscribers = new Dictionary<Guid, GeneralSubscriber>();
        protected volatile bool _disposed = false;
        protected volatile bool _shuttingDown = false;
        protected volatile GeneralExitMessage _shuttingDownMessage = null;
        protected long _MessagesQueued = 0;
#if DEBUG
        protected bool _ShutdownPostingCheck = true;
#endif

        [Conditional("DEBUG")]
        protected void ReportMessage(string reason, GeneralMessage msg)
        {
#if TRACE_MESSAGES
            Trace.TraceInformation("Message {0} - {1} messages being processed. (Msg: {2}, Id: {3})", reason,  _MessagesQueued, msg.GetType().Name, msg.MessageId);
#endif
        }

        public void PostMessageAsync(GeneralMessage msg)
        {
            if (null == msg)
                return;

#if DEBUG
            if (msg is GeneralExitMessage && _ShutdownPostingCheck)
            {
                // should be posted with PostShuttingDownMessageAsync instead
                Debugger.Break();
            }
#endif

            lock (_Subscribers)
            {
                if (_disposed)
                    throw new ObjectDisposedException(this.GetType().Name);

                //if (_shuttingDown)
                //    throw new ArgumentException("Application is shutting down.");
                msg.ReportPlacedInQueue();
                GeneralSubscriber scr = _Subscribers[msg.TargetSubscriberId];
                msg.Bus = this;
                msg.PlacedInQueue = DateTime.Now;
                msg.OnMessageProcessed += ProcessedMessageRegistrator;
                Interlocked.Increment(ref _MessagesQueued);
                scr.AddMessageAsync(msg);
                ReportMessage("added", msg);
            }
        }

        public void PostMessageSync(GeneralMessage msg)
        {
            PostMessageAsync(msg);
            msg.WaitCompleted();
        }

        public void RegisterSubscriber(GeneralSubscriber scr)
        {
            lock (_Subscribers)
            {
                if (_disposed)
                    throw new ObjectDisposedException(this.GetType().Name);
                _Subscribers[scr.SubscriberId] = scr;
                scr.Bus = this;
            }
        }
        protected void CheckShutdownConditions()
        {
            if (_MessagesQueued == 0 && _shuttingDownMessage != null && !_shuttingDown)
            {
                _shuttingDownMessage.FireOnExitProcessingStarted();
#if DEBUG
                _ShutdownPostingCheck = false;
#endif
                PostMessageAsync(_shuttingDownMessage);
                Trace.TraceInformation("Shutdown message posted.");
                _shuttingDown = true;
            }
        }
        public void PostShuttingDownMessageAsync(GeneralExitMessage msg)
        {
            Trace.TraceInformation("Shutdown message post requested.");
            lock (_Subscribers)
            {
                _shuttingDownMessage = msg;
                CheckShutdownConditions();
            }
        }
        public void ProcessedMessageRegistrator(object sender, EventArgs e)
        {
            lock (_Subscribers)
            {
                Interlocked.Decrement(ref _MessagesQueued);
                GeneralMessage msg = (GeneralMessage)sender;
                msg.OnMessageProcessed -= ProcessedMessageRegistrator;
                ReportMessage("removed", msg);
                CheckShutdownConditions();
            }
        }

        public void Dispose()
        {
            Trace.TraceInformation("Bus dispose requested.");
            lock (_Subscribers)
            {
                foreach (var kvp in _Subscribers)
                    kvp.Value.Dispose();
                _Subscribers.Clear();
                _disposed = true;
                Trace.TraceInformation("Bus disposed.");
            }
        }

        public void DisposeAsync()
        {
            new Thread((ThreadStart)Dispose).Start();
        }

        private event MessagingExceptionEventHandler _UnhandledException;
        public event MessagingExceptionEventHandler UnhandledException
        {
            add
            {
                lock (_Subscribers)
                    _UnhandledException += value;
            }
            remove
            {
                lock (_Subscribers)
                    _UnhandledException -= value;
            }
        }

        // Global exceptions sink
        // Primarily, for debugging purposes
        //
        // Use this sink in code to catch and report unexpected exceptions

        private static object _GlobalSyncObj = new object();
        private static event MessagingExceptionEventHandler _GlobalUnhandledExceptionsSink;
        public static event MessagingExceptionEventHandler GlobalUnhandledExceptionsSink
        {
            add
            {
                lock (_GlobalSyncObj)
                    _GlobalUnhandledExceptionsSink += value;
            }
            remove
            {
                lock (_GlobalSyncObj)
                    _GlobalUnhandledExceptionsSink -= value;
            }
        }
        
        public void FireExceptionHandler(object sender, MessagingException ex)
        {
            if (_UnhandledException != null)
                _UnhandledException(sender, ex);
            lock (_GlobalSyncObj)
            {
                if (_GlobalUnhandledExceptionsSink != null)
                    _GlobalUnhandledExceptionsSink(sender, ex);
            }
        }
    }
}
